package com.xy.xylive.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StreamUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xy.xylive.aop.NoSign;
import com.xy.xylive.api.R;
import com.xy.xylive.common.ServiceException;
import com.xy.xylive.common.SignatureUtil;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

@Component
@WebFilter(urlPatterns = "/*")
@Slf4j
@Order(1)
public class SignatureFilter implements Filter {

    @Value("${crypto.expire}")
    private Integer expire;

    @Value("${crypto.expire-offset}")
    private Integer expireOffset;

    @Autowired
    private SignatureUtil signatureUtil;

    private RequestMappingHandlerMapping handlerMapping;
    private Set<String> noSignPaths = new HashSet<>();
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 获取Spring上下文，初始化handlerMapping
        WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
        if (context != null) {
            handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
            log.info("SignatureFilter initialized with handlerMapping: {}", handlerMapping);
            
            // 扫描所有带有@NoSign注解的方法路径
            Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
            for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
                HandlerMethod handlerMethod = entry.getValue();
                if (handlerMethod.hasMethodAnnotation(NoSign.class)) {
                    Set<String> patterns = entry.getKey().getPatternsCondition().getPatterns();
                    noSignPaths.addAll(patterns);
                    log.info("已添加@NoSign路径: {}", patterns);
                }
            }

            // 添加静态资源路径
            noSignPaths.add("/upload/**");
            log.info("已添加静态资源路径: {}", "/upload/**");
        } else {
            log.error("Failed to get WebApplicationContext in SignatureFilter");
            throw new ServletException("SignatureFilter initialization failed: WebApplicationContext is null");
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 先检查是否为资源路径，再检查@NoSign注解
        if (shouldSkipSignatureValidation(httpRequest)) {
            chain.doFilter(httpRequest, response);
            return;
        }

        // 需要签名验证时才包装请求以缓存请求体
        CachedBodyHttpServletRequest wrappedRequest = new CachedBodyHttpServletRequest(httpRequest);

        try {

            // 获取请求参数
            Map<String, String> params = getRequestParams(wrappedRequest);
            String timestamp = wrappedRequest.getHeader("timestamp");
            String sign = wrappedRequest.getHeader("sign");

            // log打印请求路径 请求参数 时间戳 签名 写到一行log上
            log.info("请求路径: {}, 请求参数: {}, 时间戳: {}, 签名: {}", wrappedRequest.getRequestURI(), params, timestamp, sign);

            // 验证参数完整性
            if (StrUtil.isEmpty(timestamp) || StrUtil.isEmpty(sign)) {
                throw new ServiceException("签名参数不完整");
            }

            // 验证时间戳有效性
            long currentTime = System.currentTimeMillis();
            long requestTime;
            try {
                requestTime = Long.parseLong(timestamp);
            } catch (NumberFormatException e) {
                throw new ServiceException("时间戳格式错误");
            }
            long timeDiff = currentTime - requestTime;
            // log.info("时间戳差值: {}", timeDiff);
            // log.info("当前时间: {}, 请求时间: {}", currentTime, requestTime);
            // log.info("expire: {}", expire);
            if (timeDiff > expire || timeDiff < expireOffset) {
                throw new ServiceException("时间戳已过期或无效");
            }

            // 验证签名
            boolean isValid = signatureUtil.verify(params, timestamp, sign);
            if (!isValid) {
                log.error("签名验证失败");
                throw new ServiceException("签名验证失败");
            }

            // 验证通过，继续处理请求
            chain.doFilter(wrappedRequest, response);
        } catch (ServiceException e) {
            // 处理验证失败
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            httpResponse.setContentType("application/json;charset=UTF-8");
            try (PrintWriter writer = httpResponse.getWriter()) {
                writer.write(JSONUtil.toJsonStr(R.fail(e.getMessage())));
            }
        }
    }

    private boolean shouldSkipSignatureValidation(HttpServletRequest request) {
        String contextPath = request.getContextPath();
        String requestUri = request.getRequestURI();
        // 去除上下文路径
        String requestPath = contextPath.isEmpty() ? requestUri : requestUri.substring(contextPath.length());
        
        // 使用AntPathMatcher匹配路径模式
        for (String noSignPath : noSignPaths) {
            if (pathMatcher.match(noSignPath, requestPath)) {
                log.debug("请求路径 {} 匹配@NoSign路径 {}，跳过签名验证", requestPath, noSignPath);
                return true;
            }
        }
        log.debug("请求路径 {} 不匹配任何@NoSign路径，进行签名验证", requestPath);
        return false;
    }

    private Map<String, String> getRequestParams(HttpServletRequest request) throws IOException {
        Map<String, String> params = new HashMap<>();

        // 获取路径变量
        Map<String, String> pathVariables = (Map<String, String>) request.getAttribute("org.springframework.web.servlet.HandlerMapping.uriTemplateVariables");
        if (pathVariables != null) {
            params.putAll(pathVariables);
        }

        // 获取URL参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            params.put(entry.getKey(), entry.getValue()[0]);
        }

        // 获取请求体
        String contentType = request.getContentType();

        boolean isJsonType = contentType != null && (contentType.contains("application/json") || contentType.contains("application/*+json"));
        boolean isFormType = contentType != null && contentType.contains("application/x-www-form-urlencoded");

        if (isJsonType || isFormType) {
            byte[] body = StreamUtils.copyToByteArray(request.getInputStream());
            if (body.length > 0) {
                String bodyStr = new String(body, StandardCharsets.UTF_8);
                if (StrUtil.isNotEmpty(bodyStr)) {
                    ObjectMapper objectMapper = new ObjectMapper();
                    Map<String, Object> jsonBody = objectMapper.readValue(bodyStr, new TypeReference<Map<String, Object>>() {});
                    flattenJsonParams(jsonBody, "", params);
                }
            } else {
                log.warn("无法获取请求体内容，Content-Type: {}", contentType);
            }
        }

        return params;
    }

    private void flattenJsonParams(Map<String, Object> jsonBody, String parentKey, Map<String, String> params) {
        for (Map.Entry<String, Object> entry : jsonBody.entrySet()) {
            String key = StrUtil.isEmpty(parentKey) ? entry.getKey() : parentKey + "." + entry.getKey();
            Object value = entry.getValue();

            if (value instanceof Map) {
                flattenJsonParams((Map<String, Object>) value, key, params);
            } else if (value instanceof List) {
                List<?> list = (List<?>) value;
                for (int i = 0; i < list.size(); i++) {
                    Object item = list.get(i);
                    if (item instanceof Map) {
                        flattenJsonParams((Map<String, Object>) item, key + "[" + i + "]", params);
                    } else {
                        params.put(key + "[" + i + "]", item != null ? item.toString() : null);
                    }
                }
            } else {
                params.put(key, value != null ? value.toString() : null);
            }
        }
    }

    @Override
    public void destroy() {
        // 清理资源
    }

    private static class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
        private final byte[] body;

        public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
            super(request);
            this.body = StreamUtils.copyToByteArray(request.getInputStream());
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new ServletInputStream() {
                private final InputStream inputStream = new ByteArrayInputStream(body);
                
                @Override
                public int read() throws IOException {
                    return inputStream.read();
                }
                
                @Override
                public boolean isFinished() {
                    try {
                        return inputStream.available() == 0;
                    } catch (IOException e) {
                        return true;
                    }
                }
                
                @Override
                public boolean isReady() {
                    return true;
                }
                
                @Override
                public void setReadListener(ReadListener listener) {
                    // 未实现异步读取监听
                }
            };
        }
        
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    }
}
